﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Data;
using Dorm.Helpers;
using Dorm.QueryObject;
using Dorm.Services;

namespace Dorm
{
    /// <summary>
    /// The main facade for all Database related operations.
    /// It holds a database connection and all relevant methods for manipulating it.
    /// </summary>
    public class Database: IDisposable
    {
        #region Data Access
        private DataAccess dataAccess;

        public Database() { dataAccess = new DataAccess(); }

        /// <summary>
        /// Opens a new connection with the database. 
        /// Optional, since the connection is already openned when the Database class is initialized.
        /// </summary>
        public void OpenConnection() { dataAccess = new DataAccess(); }

        /// <summary>
        /// Closes the connection with the database.
        /// Optional, since the connection is closed when the Database object is disposed.
        /// </summary>
        public void CloseConnection() { dataAccess.Dispose(); }

        /// <summary>
        /// Begins a new transaction with the current connection.
        /// Optional, since the transactions can be implicit created.
        /// </summary>
        public void BeginTransaction() { dataAccess.BeginTransaction(); }

        /// <summary>
        /// Commits the current transaction on the database.
        /// Optional, since implicit transactions are automaticaly committed.
        /// </summary>
        public void CommitTransaction() { dataAccess.CommitTransaction(); }

        /// <summary>
        /// Rollback the current transaction on the database.
        /// Optional, since implicit transactions are automaticaly rollback when any exception occurs.
        /// </summary>
        public void RollbackTransaction() { dataAccess.RollbackTransaction(); }

        /// <summary>
        /// Executes a sql query and returns a DataReader with the results.
        /// </summary>
        public IDataReader ExecuteQuery(string sql) { return dataAccess.ExecuteReader(sql); }
        
        /// <summary>
        /// Executes a sql command and returns the number of registers affected.
        /// </summary>
        public int ExecuteNonQuery(string sql) { return dataAccess.ExecuteNonQuery(sql); }
        
        /// <summary>
        /// Executes a sql query and returns a single scalar value.
        /// </summary>
        public object ExecuteScalar(string sql) { return dataAccess.ExecuteScalar(sql); }
        #endregion

        #region Mapper
        
        /// <summary>
        /// Returns the first entity that meets the specified search criteria, or null if not found.
        /// </summary>
        public T First<T>(Query<T> query) { var list = List(query.Top(1)); return list.Count > 0 ? list[0] : default(T); }

        /// <summary>
        /// Returns a list of all entities that meet the specified search criteria.
        /// </summary>
        public List<T> List<T>(Query<T> query) { return new FindService(dataAccess).Find(query); }
        
        /// <summary>
        /// Inserts or Updates the specified entity on the database, cascading all its loaded children.
        /// </summary>
        public bool Save<T>(T entity) { return new SaveService<T>(dataAccess).MapAndRun(entity); }
        
        /// <summary>
        /// Deletes the specified entity on the database, cascading all its loaded children.
        /// </summary>
        public void Delete<T>(T entity) { new DeleteService(dataAccess).MapAndRun(entity); }

        /// <summary>
        /// Deletes the specified entity on the database, cascading all its loaded children.
        /// </summary>
        public void DeleteAll<T>(Query<T> query) { List(query.LoadAll()).ForEach(e => new DeleteService(dataAccess).MapAndRun(e)); }

        /// <summary>
        /// Returns whether an entity is unique on the database, based on a list of properties to check.
        /// </summary>
        public bool IsUnique<T>(T entity, params Expression<Func<T, object>>[] propertiesToCheck) { return new UniqueService(dataAccess).IsUnique(entity, propertiesToCheck); }
        
        /// <summary>
        /// Returns whether an entity exists or not in the database.
        /// </summary>
        public bool Exists<T>(long ID) { return Count<T>(new Query<T>().GetID(ID)) > 0; }

        /// <summary>
        /// Returns whether any entity matches the specified criteria.
        /// </summary>
        public bool Any<T>(Query<T> query) { return Count<T>(query) > 0; }

        /// <summary>
        /// Returns the number of records that meet the specified criteria.
        /// </summary>
        public long Count<T>(Query<T> query) { return Convert.ToInt64(ExecuteScalar(query.Count().ToString())); }

        /// <summary>
        /// Loads the specified property on a given entity, with its children.
        /// </summary>
        public void Load<T>(T entityToFill, Expression<Func<T, object>> propertyToLoad) { Load(new List<T> { entityToFill }, propertyToLoad); }
        
        /// <summary>
        /// Loads the specified property in all entities of the given list.
        /// </summary>
        public void Load<T>(List<T> entitiesToFill, Expression<Func<T, object>> propertyToLoad) { new LoadService(dataAccess).Load(LambdaParser.GetFieldName<T>(propertyToLoad), entitiesToFill); }
        
        /// <summary>
        /// Loads all collection properties of the given entity.
        /// </summary>
        public void LoadAll<T>(T entityToFill) { LoadAll(new List<T> { entityToFill }); }

        /// <summary>
        /// Loads all collection properties of each entity on a given list.
        /// </summary>
        public void LoadAll<T>(List<T> entitiesToFill) { new LoadService(dataAccess).LoadAll(entitiesToFill); }

        /// <summary>
        /// Returns a specific page on the list of all entities that meet the specified search criteria.
        /// </summary>
        public List<T> ListPage<T>(Query<T> query, int selectedPage, out long totalRecords) { return new PageService(dataAccess).ListPage(query, selectedPage, out totalRecords); }

        /// <summary>
        /// Returns a specific page on the list of all entities that meet the specified search criteria.
        /// </summary>
        public List<T> ListPage<T>(Query<T> query, int selectedPage, int pageSize, out long totalRecords) { return new PageService(dataAccess).ListPage(query, selectedPage, pageSize, out totalRecords); }

        #endregion

        #region Script
        /// <summary>
        /// Generates the Database Create Scripts for all the entities mapped.
        /// </summary>
        public string ScriptAll() { return new ScriptService().ScriptAll(); }

        /// <summary>
        /// Generates the Database Create Scripts for the specified mapped entity.
        /// </summary>
        public string ScriptTable<T>() { return new Create(typeof(T)).ToString(); }
        #endregion        

        /// <summary>
        /// Closes the current connection with the database.
        /// </summary>
        public void Dispose() { CloseConnection(); }
    }
}
